package com.yeebo.utils;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yeebo.enums.ResultCode;
import com.yeebo.exception.ServiceException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Component("redisUtils")
@Configuration
@EnableRedisHttpSession
public class RedisUtils {
    private static Logger logger = LoggerFactory.getLogger(RedisUtils.class);

    private static final String LOCK_PREFIX = "redis_lock_";

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        // 使用Jackson2JsonRedisSerialize 替换默认序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        // 设置value的序列化规则和 key的序列化规则
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    /**
     * 普通缓存放入并指定时间
     *
     * @param key   缓存键值
     * @param value 缓存值
     * @param time  缓存时间，秒,大于等于0，如果为0代表无限期
     * @return true, false
     */
    public boolean set(String key, Object value, Long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                redisTemplate.opsForValue().set(key, value);
            }
        } catch (Exception ex) {
            logger.error("Redis设置新值key={},value={}时报错，错误信息{}", key, value, ex);
        }
        return false;
    }

    public Object get(String key) {
        if (StringUtils.isBlank(key)) {
            return null;
        }
        return redisTemplate.opsForValue().get(key);
    }

    public Map<String, Object> keys(String keysPattern) {
        Set<String> keys = redisTemplate.keys(keysPattern);
        Map<String, Object> result = new HashMap<>();
        for (String key : keys) {
            if (!key.contains("shiro:")) {
                result.put(key, get(key));
            }
        }
        return result;
    }

    public Properties getRedisInfo() {
        return redisTemplate.getRequiredConnectionFactory().getConnection().info("memory");
    }

    /**
     * 最终加强分布式锁
     *
     * @param key key值
     * @param lockExpire 失效时间，毫秒
     * @return 是否获取到
     */
    public boolean lock(String key, Long lockExpire) {
        String lock = LOCK_PREFIX + key;
        return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
            long expireAt = System.currentTimeMillis() + lockExpire + 1;
            Boolean acquire = connection.setNX(lock.getBytes(), String.valueOf(expireAt).getBytes());
            if (acquire) {
                return true;
            } else {
                byte[] value = connection.get(lock.getBytes());
                if (Objects.nonNull(value) && value.length > 0) {
                    long expireTime = Long.parseLong(new String(value));
                    if (expireTime < System.currentTimeMillis()) {
                        // 如果锁已经过期
                        byte[] oldValue = connection.getSet(lock.getBytes(), String.valueOf(System.currentTimeMillis() + lockExpire + 1).getBytes());
                        // 防止死锁
                        if (Long.parseLong(new String(oldValue)) < System.currentTimeMillis()) {
                            return true;
                        }
                    }
                }
            }
            throw new ServiceException(ResultCode.REPEAT_REQUEST);
        });
    }

    /**
     * 删除锁
     *
     * @param key
     */
    public void releaseLock(String key) {
        redisTemplate.delete(LOCK_PREFIX + key);
    }

    public void delete(String key) {
        redisTemplate.delete(key);
    }

}